package com.yixun.qfbao.api.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.yixun.qfbao.api.service.SaveAttetionBindInfoService;
import com.yixun.qfbao.constant.WxConstant;
import com.yixun.qfbao.mapper.WxBindMapper;
import com.yixun.qfbao.util.PorxyResult;
import com.yixun.qfbao.util.PorxyResult.HStatus;
import com.yixun.qfbao.util.PorxyUtil;
import com.yixun.qfbao.util.RestTemplateUtil;
import com.yixun.qfbao.util.WxAccessTokenCacheUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 用来放置weixinService不适合放置的方法。
 * 	暂时是由于根据不同的公司获取用户列表异步调用。解决同类调用@async失效的问题
 * @author zilong.liu
 * @date 2020.9.17
 * 	
 */
@Slf4j
@Service
public class SaveAttetionBindInfoServiceImpl implements SaveAttetionBindInfoService{

    @Autowired
    private WxAccessTokenCacheUtils wxAccessTokenCacheUtils;
	
    @Autowired
    WxBindMapper wxBindMapper;
    /**
	 * 保存已经关注的用户信息至tm_wx_bind表中
	 * @param appIdInfo
	 */
//	@Async
    @Override
    @SuppressWarnings("unchecked")
	public void saveAttetionBindInfo(Map<String, Object> appIdInfo) {
    	
    	String appId = String.valueOf(appIdInfo.get("app_id"));
    	String companyUuid = String.valueOf(appIdInfo.get("company_uuid"));
    	String systemId = String.valueOf(appIdInfo.get("system_id"));
    	log.info("SaveAttetionBindInfoServiceImpl->saveAttetionBindInfo->本次处理appId相关信息为：{}", appIdInfo);
    	//1、获取对应的accessToken
    	String accessToken = wxAccessTokenCacheUtils.getAccessTokenBySystemId(companyUuid, systemId, ""); 
    	if(accessToken == null) {
    		return;
    	}
    	String nextOpenId = "";
    	do {
    		//2、获取所有的用户列表
    		JSONObject userListResult = getUserListResult(accessToken, nextOpenId, 0, companyUuid, systemId);
        	if(userListResult == null) {
        		log.info("SaveAttetionBindInfoServiceImpl->saveAttetionBindInfo->本次处理jsonResult返回为空");
        		return;
        	}
        	accessToken = userListResult.getString("accessToken");
        	String returnNextOpenId = String.valueOf(userListResult.get("next_openid"));
        	nextOpenId = "null".equals(returnNextOpenId)? "": returnNextOpenId;
        	if("0".equals(String.valueOf(userListResult.get("count")))) return;
        	Object openIds = userListResult.getJSONObject("data").get("openid");
        	List<String> openIdList = openIds == null? null: (List<String>)openIds;
        	if(CollectionUtils.isEmpty(openIdList)) {
        		return;
        	}
        	//现在比较少。等到生产的时候需要通过日志判断不同的APPID的数据的完整性
        	log.info("SaveAttetionBindInfoServiceImpl->saveAttetionBindInfo->本次返回的已关注列表的长度为：{}", openIdList.size());
        	//受到微信限制，单次最多查询100个
        	for(int i = 0; i < openIdList.size(); i += 100) {
        		//3、获取所有的用户信息,分段获取，分段插入
        		List<String> toGetInfoOpenIdList = new ArrayList<>();
        		for(int j = i; (j < i+100 && j < openIdList.size()); j ++) {
        			toGetInfoOpenIdList.add(openIdList.get(j));
        		}
        		log.info("SaveAttetionBindInfoServiceImpl->saveAttetionBindInfo->本次待查询的用户基本新的openId集合为:{}", toGetInfoOpenIdList);
        		List<Map<String, Object>> userInfoList = batchGetUserInfos(accessToken, toGetInfoOpenIdList, 0, companyUuid, systemId);
        		//4、更新至对应的表中。（不需要事务）
        		if(CollectionUtils.isEmpty(userInfoList)) {
        			log.info("SaveAttetionBindInfoServiceImpl->saveAttetionBindInfo->获取到的用户信息为空，请排查");
        			return;
        		}
        		int insertResult = batchInsertUserInfo(userInfoList, appId);
        		log.info("SaveAttetionBindInfoServiceImpl->saveAttetionBindInfo->本次查询数据共{}条，插入数据库成功数据共{}条", toGetInfoOpenIdList.size(), insertResult);
        		if(insertResult != userInfoList.size()) {
        			log.error("SaveAttetionBindInfoServiceImpl->saveAttetionBindInfo->本次核算数据量插入异常，请排查");
        			log.error("SaveAttetionBindInfoServiceImpl->saveAttetionBindInfo->toGetInfoOpenIdList={}", toGetInfoOpenIdList);
        			log.error("SaveAttetionBindInfoServiceImpl->saveAttetionBindInfo->userInfoList={}", userInfoList);
        		}
        	}
    	}while(!"".equals(nextOpenId));
	}
    
	
    
    //调用微信接口，因为accessToken失效的最大尝试次数
    private static final int MAX_RETRY_TIMES = 3;
    
    /**
     * 获取用户列表
     * @param accessToken
     * @param nextOpenId 第一次为空，上次返回的nextOpenId
     * @param retryTimes 重新尝试次数，受到变量控制
     * @return 返回为空时，则代表获取userList失败或者当前的nextOpenId后无可继续获取的openId
     * 
     */
    private JSONObject getUserListResult(String accessToken, String nextOpenId, 
    		int retryTimes, String companyUuid, String systemId) {
    	String url = WxConstant.GET_USER_LIST;
    	url = url.replace("ACCESS_TOKEN", accessToken);
    	url = url.replace("NEXT_OPENID", nextOpenId);
    	log.info("SaveAttetionBindInfoServiceImpl->getUserList->url:", url);
    	PorxyResult porxyResult = PorxyUtil.getPorxy(url);
    	if(!HStatus.YES.getNo().equals(porxyResult.getStatus().getNo())) {
    		return null;
    	}
    	
    	String result = porxyResult.getContext();
    	JSONObject jsonResult = JSONObject.parseObject(result);
    	//存在errorCode， 则代表此次查询异常，需要记录异常时对应的next_openId，方便下次刷新时从当前openId
    	Object errorCode = jsonResult.get("errcode");
    	if("40001".equals(String.valueOf(errorCode))) {
    		if(retryTimes <= MAX_RETRY_TIMES) {
    			retryTimes ++;
    			accessToken = castRefershToken(companyUuid, systemId);
    			return this.getUserListResult(accessToken, nextOpenId, retryTimes, companyUuid, systemId);
    		}else {
    			return null;
    		}
    	}
    	if(errorCode != null) {
    		return null;
    	}
    	jsonResult.put("accessToken", accessToken);
    	return jsonResult;
    }
    
    private String castRefershToken(String companyUuid, String systemId) {
    	wxAccessTokenCacheUtils.remove(companyUuid, systemId);
		return wxAccessTokenCacheUtils.getAccessTokenBySystemId(companyUuid, systemId, "");
    }
	
    /**
     * 批量获取用户信息
     * 集合中传入的元素，一次最多只能有100个
     * @return
     */
    private List<Map<String, Object>> batchGetUserInfos(String accessToken, List<String> openIdList, 
    		int retryTimes, String companyUuid, String systemId){
    	if(openIdList.size() > 100) {
    		log.info("batchGetUserInfos->本次调用批量获取用户基本信息方法传递参数超过100");
    		return null;
    	}
    	String url = WxConstant.BATCH_GET_USER_INFO.replace("ACCESS_TOKEN", accessToken);
    	JSONArray userList = new JSONArray();
    	for (String openId : openIdList) {
    		JSONObject jsonObject = new JSONObject();
    		jsonObject.put("openid", openId);
    		jsonObject.put("lang", WxConstant.LANG);
    		userList.add(jsonObject);
		}
    	
    	JSONObject params = new JSONObject();
    	params.put("user_list", userList);
    	
    	String result = RestTemplateUtil.post(url, params);
    	//获取返回值，如果是40001，则重新获取accessToken，然后请求
    	JSONObject userListInfoJson = JSONObject.parseObject(result);
    	Object errorCode = userListInfoJson.get("errcode");
    	if("40001".equals(String.valueOf(errorCode))) {
    		if(retryTimes < MAX_RETRY_TIMES) {
    			retryTimes ++;
    			accessToken = castRefershToken(companyUuid, systemId);
    			return this.batchGetUserInfos(accessToken, openIdList, retryTimes, companyUuid, systemId);
    		}else {
    			log.error("SaveAttetionBindInfoServiceImpl->batchGetUserInfos->获取微信失败:{}", result);
    			return null;
    		}
    	}
    	return packUserListInfo(userListInfoJson);
    }
    
    /**
     * 组装返回的数据
     * @param userListInfoJson
     * @return
     */
    private List<Map<String, Object>> packUserListInfo(JSONObject userListInfoJson) {
    	Object errorCode = userListInfoJson.get("errcode");
    	if(errorCode != null) {
    		return null;
    	}
    	// 可以正常获取到数据，则正常拼接数据，然后返回
    	JSONArray userInfoJsonArray = userListInfoJson.getJSONArray("user_info_list");
    	List<Map<String, Object>> userInfoList = new ArrayList<>();
    	for (int i = 0; i < userInfoJsonArray.size(); i ++) {
			JSONObject userInfoJson = userInfoJsonArray.getJSONObject(i);
			Map<String, Object> userInfoMap = new HashMap<>();
			String subscribe = String.valueOf(userInfoJson.get("subscribe"));
			if("1".equals(subscribe)) {
				userInfoMap.put("attentionStatus", 2);
			} else if("0".equals(subscribe)) {
				userInfoMap.put("attentionStatus", 1);
			}
			userInfoMap.put("wxOpenId", userInfoJson.getString("openid"));
			userInfoMap.put("unionId", userInfoJson.get("unionid") == null? "": userInfoJson.get("unionid"));
			userInfoList.add(userInfoMap);
		}
    	return userInfoList;
    }
    
    /**
     * 批量插入
     * @param userInfoList
     */
    private int batchInsertUserInfo(List<Map<String, Object>> userInfoList, String appId) {
    	Map<String, Object> userInfosMap = new HashMap<>();
    	userInfosMap.put("userInfoList", userInfoList);
    	userInfosMap.put("appId", appId);
    	return wxBindMapper.batchInsert(userInfosMap);
    }
}
